/*
 * Copyright (c) 2003-2010, Live Media Pty. Ltd.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted
 * provided that the following conditions are met:
 *
 *  1. Redistributions of source code must retain the above copyright notice, this list of
 *     conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright notice, this list of
 *     conditions and the following disclaimer in the documentation and/or other materials
 *     provided with the distribution.
 *  3. Neither the name of Live Media nor the names of its contributors may be used to endorse
 *     or promote products derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package com.colony.networkvm.proxy;

import java.io.IOException;

import com.argot.TypeElement;
import com.argot.TypeException;
import com.argot.TypeInputStream;
import com.argot.TypeOutputStream;
import com.argot.TypeReader;
import com.argot.TypeWriter;
import com.argot.common.UInt16;
import com.argot.remote.MetaInterface;
import com.argot.remote.MetaMethod;
import com.argot.remote.MetaParameter;
import com.colony.networkvm.NvmProxyDelegate;
import com.colony.networkvm.instruction.NvmInstruction;
import com.colony.networkvm.machine.NvmInstance;
import com.colony.remote.RemoteException;

public class NvmMetaMethodCall 
implements NvmInstruction
{
    public NvmMetaMethodCall()
    {
        // dummy for TypeWriter.
    }

    public static final String TYPENAME = "nvm.instruction.meta_method_call";
	
	private int _methodId;

	
	public NvmMetaMethodCall( int methodId )
	{
		this._methodId = methodId;
	}

    public void execute(NvmInstance request) 
    throws RemoteException
    {
    	request.incProgramCounter();
        try
        {
        	// The object to call is on the stack.
            Object obj = request.pop();
            
            // find the method we want to call.
            MetaMethod mMethod = (MetaMethod) request.getTypeLibrary().getStructure( _methodId );
            
            MetaParameter[] requestTypes = mMethod.getRequestTypes();
            
            // pop arguments off the stack.            
            Object[] args = new Object[ requestTypes.length ];
            for (int x=0; x< requestTypes.length; x++ )
            {
            	// would be nice to check requestType ids to stack ids.
            	// problem is that we may have abstract/concrete type mistmatches.
            	
            	args[x] = request.pop();
            }
            
            
            // prepare the call.
            NvmMetaMethodRequest methodRequest = new NvmMetaMethodRequest( mMethod, args );
            
    		NvmProxyDelegate back = request.getEnvironment().getBackDelegate( obj.getClass() );
    		if ( back == null )
    		{
    			throw new RemoteException( "no server delegate to invoke method" );
    		}
            back.processProxy( methodRequest, obj );
            
            Throwable e = methodRequest.getException();
            if ( e != null )
            {
            	request.setException(e);
            }
            else
            {
            	Object r = methodRequest.getReturnValue();
            	if ( r == null )
            	{
            		request.push(r);
            		return;
            	}
            	
            	MetaParameter[] returnTypes = mMethod.getResponseTypes();
            	if (returnTypes.length!=1)
            	{
            		throw new RemoteException("Unable to perform multiple returns");
            	}
            	
            	int[] ids = request.getTypeLibrary().getId(r.getClass());
            	int returnType = returnTypes[0].getParamType();
            	int metaInterfaceId = request.getTypeLibrary().getTypeId(MetaInterface.TYPENAME);
            	
            	for(int x=0;x<ids.length;x++)
            	{
            		if (ids[x]==returnType)
            		{
            			request.push( ids[x], r );
            			return;
            		}
            		
            		// Check for return by Reference.  If this object is defined by an interface
            		// then we need to return by reference, however, we don't know the object location.
            		TypeElement structure = request.getTypeLibrary().getStructure(ids[x]);
            		if (structure.getTypeId()==metaInterfaceId)
            		{
                		throw new RemoteException("Unable to return interface by value:" + r.getClass().getName() );            		

            		}
            	}
            	
            	Class clss = request.getTypeLibrary().getClass(returnType);
            	if (clss!=null)
            	{
            		if ( clss.isAssignableFrom(r.getClass()))
            		{
            			request.push( ids[0], r );
            			return;
            		}
            		
            		throw new RemoteException("No matching return type bound for class:" + r.getClass().getName() + " to " + clss.getName() );            		
            	}
            	throw new RemoteException("No matching return type bound for class:" + r.getClass().getName() );
            	
            }
        }
        catch (TypeException e)
        {  
            e.printStackTrace();
            request.setException(e);
        } 
        catch (RemoteException e)
        {
            e.printStackTrace();
            request.setException(e);
        }
 		
    }

    public static class NvmMetaMethodCallTypeWriter
    implements TypeWriter
    {
	    public void write(TypeOutputStream out, Object o) throws TypeException, IOException
	    {
	       NvmMetaMethodCall call = (NvmMetaMethodCall)o;
	       int id = call._methodId;
	       out.writeObject( UInt16.TYPENAME, new Integer( id ));
	    }
    }

    public static class NvmMetaMethodCallTypeReader
    implements TypeReader
    {
	    public Object read(TypeInputStream in) throws TypeException, IOException
	    {
	        Integer methodId = (Integer) in.readObject(UInt16.TYPENAME );
	        return new NvmMetaMethodCall( methodId.intValue() );
	    }
    }

}
